package ca.scotthyndman.as2haxe.dom;

import java.util.ArrayList;
import java.util.List;

import ca.scotthyndman.as2haxe.dom.visitor.IVisitor;

/**
 * Local variable or field declaration statement.
 */
public class ASVariableDeclaration extends ASTNode {

	/**
	 * The "name" structural property of this node type.
	 * 
	 * @since 3.0
	 */
	public static final ASChildPropertyDescriptor NAME_PROPERTY = new ASChildPropertyDescriptor(
			ASVariableDeclaration.class, "name", ASSimpleName.class, true);

	/**
	 * The "type" structural property of this node type
	 */
	public static final ASChildPropertyDescriptor TYPE_PROPERTY = new ASChildPropertyDescriptor(
			ASVariableDeclaration.class, "type", ASType.class, true);

	/**
	 * The "modifiers" structural property of this node type
	 */
	public static final ASSimplePropertyDescriptor MODIFIERS_PROPERTY = new ASSimplePropertyDescriptor(
			ASVariableDeclaration.class, "modifiers", int.class, true); //$NON-NLS-1$

	/**
	 * The "initializer" structural property of this node type.
	 * 
	 * @since 3.0
	 */
	public static final ASChildPropertyDescriptor INITIALIZER_PROPERTY = new ASChildPropertyDescriptor(
			ASVariableDeclaration.class,
			"initializer", ASExpression.class, false); //$NON-NLS-1$

	/**
	 * A list of property descriptors (element type:
	 * {@link ASPropertyDescriptor}), or null if uninitialized.
	 */
	private static final List<ASPropertyDescriptor> PROPERTY_DESCRIPTORS;

	static {
		List<Object> properyList = new ArrayList<Object>(5);
		createPropertyList(ASVariableDeclaration.class, properyList);
		addProperty(NAME_PROPERTY, properyList);
		addProperty(INITIALIZER_PROPERTY, properyList);
		addProperty(MODIFIERS_PROPERTY, properyList);
		addProperty(TYPE_PROPERTY, properyList);
		PROPERTY_DESCRIPTORS = reapPropertyList(properyList);
	}

	/**
	 * Returns a list of structural property descriptors for this node type.
	 * Clients must not modify the result.
	 * 
	 * @return a list of property descriptors (element type:
	 *         {@link ASPropertyDescriptor})
	 */
	public static List<ASPropertyDescriptor> propertyDescriptors() {
		return PROPERTY_DESCRIPTORS;
	}

	//
	// ======== MEMBERS
	//

	/** Modifiers on this variable */
	private int modifierFlags = ASModifier.NONE;

	/** The name of this variable */
	private ASSimpleName name;

	/** Name of this variable's type. */
	private ASType type;

	/** The variable's initializer. */
	private ASExpression initializer;

	/**
	 * Constructs a new instance of ASVariableDeclaration
	 * 
	 * @param ast
	 *            The abstract syntax tree that owns this element
	 */
	public ASVariableDeclaration(AST ast) {
		super(ast);
	}

	/**
	 * Returns the name of this variable's type.
	 * 
	 * @return The type's name
	 */
	public ASType getType() {
		return type;
	}

	/**
	 * Sets the name of this variable's type.
	 * 
	 * @param type
	 *            The type's new name
	 */
	public void setType(ASType type) {
		if (type == null) {
			throw new IllegalArgumentException();
		}
		ASTNode oldChild = this.type;
		preReplaceChild(oldChild, type, TYPE_PROPERTY);
		this.type = type;
		postReplaceChild(oldChild, type, TYPE_PROPERTY);
	}

	/**
	 * Returns the modifiers for this variable. {@link ASModifier#NONE} will be
	 * returned if the variable is local.
	 * 
	 * @return The variable's modifiers
	 */
	public int getModifiers() {
		return modifierFlags;
	}

	/**
	 * Sets the modifiers for this variable. {@link ASModifier#NONE} is the only
	 * valid value for a local variable.
	 * 
	 * @return The variable's modifiers
	 */
	public void setModifiers(int pmodifiers) {
		preValueChange(MODIFIERS_PROPERTY);
		this.modifierFlags = pmodifiers;
		postValueChange(MODIFIERS_PROPERTY);
	}

	/**
	 * Returns the initializer of this variable declaration, or
	 * <code>null</code> if there is none.
	 * 
	 * @return the initializer expression node, or <code>null</code> if there
	 *         is none
	 */
	public ASExpression getInitializer() {
		return initializer;
	}

	/**
	 * Sets or clears the initializer of this variable declaration.
	 * 
	 * @param initializer
	 *            the initializer expression node, or <code>null</code> if
	 *            there is none
	 */
	public void setInitializer(ASExpression initializer) {
		ASTNode oldChild = this.initializer;
		preReplaceChild(oldChild, initializer, TYPE_PROPERTY);
		this.initializer = initializer;
		postReplaceChild(oldChild, initializer, TYPE_PROPERTY);
	}

	/**
	 * Returns the name of the variable declared in this variable declaration.
	 * 
	 * @return the variable name node
	 */
	public ASSimpleName getName() {
		return name;
	}

	/**
	 * Sets the name of the variable declared in this variable declaration to
	 * the given name.
	 * 
	 * @param variableName
	 *            the new variable name
	 */
	public void setName(ASSimpleName variableName) {
		if (variableName == null) {
			throw new IllegalArgumentException();
		}
		ASTNode oldChild = this.name;
		preReplaceChild(oldChild, variableName, NAME_PROPERTY);
		this.name = variableName;
		postReplaceChild(oldChild, variableName, NAME_PROPERTY);
	}

	//
	// ======== INTERNAL GETTER AND SETTER
	//

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	int internalGetSetIntProperty(ASSimplePropertyDescriptor property,
			boolean get, int value) {
		if (property == MODIFIERS_PROPERTY) {
			if (get) {
				return getModifiers();
			} else {
				setModifiers(value);
				return 0;
			}
		}

		// allow default implementation to flag the error
		return super.internalGetSetIntProperty(property, get, value);
	}

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	final ASTNode internalGetSetChildProperty(
			ASChildPropertyDescriptor property, boolean get, ASTNode child) {
		if (property == NAME_PROPERTY) {
			if (get) {
				return getName();
			} else {
				setName((ASSimpleName) child);
				return null;
			}
		}
		if (property == INITIALIZER_PROPERTY) {
			if (get) {
				return getInitializer();
			} else {
				setInitializer((ASExpression) child);
				return null;
			}
		}
		if (property == TYPE_PROPERTY) {
			if (get) {
				return getType();
			} else {
				setType((ASType) child);
				return null;
			}
		}

		// allow default implementation to flag the error
		return super.internalGetSetChildProperty(property, get, child);
	}

	// ====== PARENT SETTING

	@Override
	public void setParent(ASTNode parent, ASPropertyDescriptor location) {
		super.setParent(parent, location);

		IScopeElement scope = getNearestScope();
		if (scope == null) {
			return;
		}

		scope.addToScope(this);
	}

	// ====== CONVENIENCE METHODS

	public boolean isPublic() {
		return (modifierFlags & ASModifier.PUBLIC) != 0;
	}

	public boolean isPrivate() {
		return (modifierFlags & ASModifier.PRIVATE) != 0;
	}

	public void setPrivate(boolean isPrivate) {
		if (isPrivate) {
			this.modifierFlags |= ASModifier.PRIVATE;
			this.modifierFlags &= ~ASModifier.PUBLIC;
		} else {
			this.modifierFlags |= ASModifier.PUBLIC;
			this.modifierFlags &= ~ASModifier.PRIVATE;
		}
	}

	public boolean isStatic() {
		return (modifierFlags & ASModifier.STATIC) != 0;
	}

	public void setStatic(boolean isStatic) {
		if (isStatic) {
			this.modifierFlags |= ASModifier.STATIC;
		} else {
			this.modifierFlags &= ~ASModifier.STATIC;
		}
	}

	// ====== INTERFACE METHODS

	public void accept(IVisitor as2Visitor, boolean recursive) {
		recursive = as2Visitor.visit(this);

		if (recursive) {
			getName().accept(as2Visitor, recursive);
			getType().accept(as2Visitor, recursive);
			if (getInitializer() != null) {
				getInitializer().accept(as2Visitor, recursive);
			}
		}
	}

	// ====== TO STRING

	@Override
	public String toString() {
		String ret = name + ":" + type.toString();
		if (getInitializer() != null) {
			ret += " = " + getInitializer().toString();
		}
		return ret;
	}
}
